﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Vibz.Common.Channel;
using System.Net.Sockets;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Threading;
using System.Reflection;
using Vibz.Common;
using System.Globalization;
using Vibz.Common.Model;

namespace Vibz.Server
{
    internal class ServerManager
    {
        SocketServer socketServer;
        public IModelManager ModelManager { get; set; }
        static DateTime lastBroadcast = DateTime.MinValue;
        static DateTime lastReceipt = DateTime.MinValue;
        string ModelName { get; set; }
        static Timer timerBroadcast;
        public ServerManager(string modelName, IModelManager modelManager)
        {
            ModelName = modelName;
            ModelManager = modelManager;
        }
        public string Init()
        {
            socketServer = new SocketServer();

            socketServer.DataReceived += new SocketPublisher.DataReceiveHandler(socketServer_DataReceived);
            socketServer.ConnectionRequested += new SocketServer.OnConnection(socketServer_ConnectionRequested);
            socketServer.DataRequested += new SocketServer.OnDataRequest(socketServer_DataRequested);

            return socketServer.ConnectionPath;
        }

        public void Stop()
        {
            timerBroadcast.Change(Timeout.Infinite, Timeout.Infinite);
        }
        public void Start()
        {
            System.Threading.TimerCallback cb = new System.Threading.TimerCallback(ProcessTimerEvent);
            timerBroadcast = new System.Threading.Timer(cb, new object[] { socketServer, ModelManager }, 4000, 2000);        
        }
        private void ProcessTimerEvent(object obj)
        {
            try
            {
                if (lastBroadcast > lastReceipt)
                    return;
                
                    SocketServer svr = (SocketServer)((object[])obj).GetValue(0);
                    IModelManager gManager = (IModelManager)((object[])obj).GetValue(1);
                    lock (gManager)
                    {
                        gManager.ProcessPeriodicEvent();
                        if (svr.Clients.Count != 0)
                        {
                            svr.Broadcast((object)gManager.Model);
                            gManager.Clear();
                            lastBroadcast = DateTime.Now;
                            Console.WriteLine("Latest updates broadcasted to all client.");
                        }
                        else
                        {
                            Console.WriteLine("No client available. Waiting for a client to connect...");
                            Stop();
                        }
                    }
            }
            catch (Exception exc)
            {
                Stop();
                // Do not throw
                //throw new Exception("Error occured while broadcasting. ", exc);
                Exception tempExc = exc;
                string message = tempExc.Message;
                while (tempExc.InnerException != null)
                {
                    message += "\r\n" + tempExc.InnerException.Message + "\r\n" + tempExc.InnerException.StackTrace;
                    tempExc = tempExc.InnerException;
                }
                
                Console.WriteLine("Error occured while broadcasting. " + message);
            }
        }
        
        void socketServer_ConnectionRequested(Socket socket, EventArgs e)
        {
            Console.WriteLine("Connected with - " + socket.RemoteEndPoint.ToString());
            ModelManager.CreateClient(socket.RemoteEndPoint.ToString());
            lastReceipt = DateTime.Now;
            Start();
        }

        void socketServer_DataReceived(Socket socket, byte[] receivedData, EventArgs e)
        {
            try
            {
                // TODO: Delete 3 lines in prod
                string message = Encoding.ASCII.GetString(receivedData, 0, receivedData.Length);
                message = message.Trim(new char[] { '\0'});
                Console.WriteLine("Received data from " + socket.RemoteEndPoint.ToString() + ":" + message);

                List<KeyValuePair<DataPacketHeader, string>> packets = DataPacket.Segregate(receivedData);
                foreach (KeyValuePair<DataPacketHeader, string> packet in packets)
                {
                    SynchronizeData(socket.RemoteEndPoint.ToString(), packet.Key, Encoding.ASCII.GetBytes(packet.Value));
                }
                lastReceipt = DateTime.Now;
            }
            catch (Exception exc)
            {
                Console.WriteLine("Error occured. <{0}> {1} {2}", socket.RemoteEndPoint, receivedData, exc.Message);
            }
        }
        internal void Save()
        {
            string folderName = "SavedModels";
            Directory.CreateDirectory(folderName);
            File.WriteAllBytes(folderName + "\\" + ModelName + "_" + 
                DateTime.Now.ToString("yyyy-MM-dd-HH-mm", 
                CultureInfo.InvariantCulture), DataPacket.Serialize(ModelManager.Model));
        }
        void SynchronizeData(string endPoint, DataPacketHeader header, byte[] byteArray)
        {
            object recObj = DataPacket.DeserializeUnknown(header.DataType, byteArray);
            switch (header.Command)
            { 
                case "Save":
                    Save();
                    break;
                default:
                    lock (ModelManager.Model)
                    {
                        switch (header.Command)
                        {
                            default:
                                ModelManager.Update(endPoint, header, recObj);
                                break;
                        }
                    }
                    break;
            }
        }
        
        byte[] socketServer_DataRequested(EventArgs e)
        {
            return DataPacket.Serialize(ModelManager.Model);
        }
    }
}
